#include "lexer_fsm.h"
#include "common/utils.h"
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>

namespace lexer {


// use shared ptr to avoid hand invoking "new" and "delete".
std::shared_ptr<AtomicState> AtomicState::STATE_0 = std::make_shared<AtomicState_0>();
std::shared_ptr<AtomicState> AtomicState::STATE_1 = std::make_shared<AtomicState_1>();
std::shared_ptr<AtomicState> AtomicState::STATE_2 = std::make_shared<AtomicState_2>();
std::shared_ptr<AtomicState> AtomicState::STATE_3 = std::make_shared<AtomicState_3>();
std::shared_ptr<AtomicState> AtomicState::STATE_4 = std::make_shared<AtomicState_4>();
std::shared_ptr<AtomicState> AtomicState::STATE_5 = std::make_shared<AtomicState_5>();
std::shared_ptr<AtomicState> AtomicState::STATE_6 = std::make_shared<AtomicState_6>();
std::shared_ptr<AtomicState> AtomicState::STATE_7 = std::make_shared<AtomicState_7>();
std::shared_ptr<AtomicState> AtomicState::STATE_8 = std::make_shared<AtomicState_8>();

void FiniteStateMachine::Start() 
{
    mContextIndex = 0;
    mIsOperating = false;
    mTokens.clear();
    mNextState = nullptr;
    this->RunState(AtomicState::STATE_0);
}

void FiniteStateMachine::RunState(std::shared_ptr<AtomicState> state) 
{
    // When a RunState() was invoked in another RunState(), it 
    // would just set the mNextState and return, so the invoking
    // stack will not be too deep. And when the currentState has
    // finally been executed, the while loop will keep executing 
    // the mNextState. The mNextState was set during executing  
    // currentState to make sure what state actually is after it.

    // set the next state to be executed.
    mNextState = std::move(state);
    if (!mIsOperating)
    {
        // it keeps executing AtomicStates until the mNextState is null.
        // that means there is no state to be executed after that.
        mIsOperating = true;
        while (mNextState != nullptr) 
        {
            auto currentState = mNextState;
            mNextState = nullptr;
            currentState->Execute(this);
        }
        mIsOperating = false;
    }
}

const std::vector<common::WordToken> &FiniteStateMachine::GetTokens() 
{
    return mTokens;
}

const std::string &FiniteStateMachine::GetSrcCode() 
{
    return mSrcCode;
}

std::string FiniteStateMachine::GetContext() 
{
    return mContext.str();
}

int FiniteStateMachine::GetContextIndex() const 
{
    return mContextIndex;
}

void FiniteStateMachine::IncreaseContext() 
{
    mContext << mSrcCode[mContextIndex];
    ++mContextIndex;
}

void FiniteStateMachine::AddToken(common::WordType tp) 
{
    // collect the token and store it, and clear the context buffer.
    common::WordToken tk(mContext.str(), tp);
    mContext.str("");
    mTokens.push_back(tk);
}

void FiniteStateMachine::SetSrcCode(const std::string &src) 
{
    mSrcCode = src;
}

void FiniteStateMachine::SetContextIndex(int idx) 
{
    mContextIndex = idx;
}

using namespace std;

void AtomicState_0::Execute(FiniteStateMachine *fsmContext) 
{
    // init state
    int index = fsmContext->GetContextIndex();
    auto src = fsmContext->GetSrcCode();

    int n = src.size();
    while (index < n && common::IsBlank(src[index])) 
    {
        index++;
    }
    if (index >= n)
    {
        return;
    }

    fsmContext->SetContextIndex(index);

    // next-state transfer.
    char c = src[index];
    if (common::IsLetter(c)) 
    {
        fsmContext->RunState(AtomicState::STATE_1);
    } 
    else if (common::IsDigit(c)) 
    {
        fsmContext->RunState(AtomicState::STATE_3);
    } 
    else if (common::IsSeparator(c)) 
    {
        fsmContext->RunState(AtomicState::STATE_8);
    } 
    else if (c == '<' || c == '>' || c == '=') 
    {
        fsmContext->RunState(AtomicState::STATE_5);
    } 
    else if (common::IsOperator(c)) 
    {
        fsmContext->RunState(AtomicState::STATE_7);
    } 
    else 
    {
        // ERROR.
        for (auto r : fsmContext->GetTokens()) 
        {
            Printer(r.type, r.word);
        }
        auto msg = std::string("Unrecognized Character: ");
        msg.push_back(c);
        throw runtime_error(msg);
    }
}

void AtomicState_1::Execute(FiniteStateMachine *fsmContext)
{
    auto src = fsmContext->GetSrcCode();

    int n = src.size();
    while (
        fsmContext->GetContextIndex() < n && (
            common::IsLetter(src[fsmContext->GetContextIndex()]) || 
            common::IsDigit(src[fsmContext->GetContextIndex()]) ) )
    {
        fsmContext->IncreaseContext();
    }
    
    fsmContext->RunState(AtomicState::STATE_2);
}

void AtomicState_2::Execute(FiniteStateMachine *fsmContext) 
{
    // keywords & identifiers
    int n = fsmContext->GetSrcCode().size();
    auto ss_str = fsmContext->GetContext();
    if (common::IsKey(ss_str)) 
    {
        fsmContext->AddToken(common::KEYWORD);
    } 
    else 
    {
        fsmContext->AddToken(common::IDENTIFIER);
    }
    if (fsmContext->GetContextIndex() >= n)
    {
        return;
    }
    fsmContext->RunState(AtomicState::STATE_0);
}

void AtomicState_3::Execute(FiniteStateMachine *fsmContext) 
{
    auto src = fsmContext->GetSrcCode();

    int n = src.size();
    while (
        fsmContext->GetContextIndex() < n && 
        common::IsDigit(src[fsmContext->GetContextIndex()]) )
    {
        fsmContext->IncreaseContext();
    }
    fsmContext->RunState(AtomicState::STATE_4);
}

void AtomicState_4::Execute(FiniteStateMachine *fsmContext) 
{
    // numeric constants
    int n = fsmContext->GetSrcCode().size();
    fsmContext->AddToken(common::CONSTANT);
    if (fsmContext->GetContextIndex() >= n) 
    {
        return;
    }
    fsmContext->RunState(AtomicState::STATE_0);
}

void AtomicState_5::Execute(FiniteStateMachine *fsmContext) 
{
    // operators of >|<|=
    auto src = fsmContext->GetSrcCode();
    int n = src.size();
    if (fsmContext->GetContextIndex() + 1 < n && 
        src[fsmContext->GetContextIndex() + 1] == '=') 
    {
        fsmContext->IncreaseContext();
        fsmContext->IncreaseContext();
        fsmContext->RunState(AtomicState::STATE_6);
    } 
    else
    {
        // it can be medium-state and also final-state.
        fsmContext->IncreaseContext();
        fsmContext->AddToken(common::OPERATOR);
        if (fsmContext->GetContextIndex() >= n) 
        {
            return;
        }
        fsmContext->RunState(AtomicState::STATE_0);
    }
}

void AtomicState_6::Execute(FiniteStateMachine *fsmContext) 
{
    // operators of >=|<=|==
    int n = fsmContext->GetSrcCode().size();
    fsmContext->AddToken(common::OPERATOR);
    if (fsmContext->GetContextIndex() >= n) 
    {
        return;
    }
    fsmContext->RunState(AtomicState::STATE_0);
}

void AtomicState_7::Execute(FiniteStateMachine *fsmContext) 
{
    // other operators.
    // it can be medium-state and also final-state.
    int n = fsmContext->GetSrcCode().size();
    fsmContext->IncreaseContext();
    fsmContext->AddToken(common::OPERATOR);
    if (fsmContext->GetContextIndex() >= n) 
    {
        return;
    }
    fsmContext->RunState(AtomicState::STATE_0);
}

void AtomicState_8::Execute(FiniteStateMachine *fsmContext) 
{
    // separators.
    // it can be medium-state and also final-state.
    int n = fsmContext->GetSrcCode().size();
    fsmContext->IncreaseContext();
    fsmContext->AddToken(common::SEPARATOR);
    if (fsmContext->GetContextIndex() >= n) 
    {
        return;
    }
    fsmContext->RunState(AtomicState::STATE_0);
}

} // namespace lexer